Skip to content

fix: block SSRF via IPv6 addresses embedding a non-global IPv4 (NAT64/IPv4-compatible)#6347

Open
adilburaksen wants to merge 2 commits into
google:mainfrom
adilburaksen:security/ssrf-embedded-ipv4-block
Open

fix: block SSRF via IPv6 addresses embedding a non-global IPv4 (NAT64/IPv4-compatible)#6347
adilburaksen wants to merge 2 commits into
google:mainfrom
adilburaksen:security/ssrf-embedded-ipv4-block

Conversation

@adilburaksen

Copy link
Copy Markdown
Contributor

Summary

load_web_page vets every resolved address with _is_blocked_address, which currently rejects an address only when not address.is_global. For IPv6, is_global on the outer address does not reflect the reachability of an embedded IPv4 target:

Address is_global Embedded IPv4
64:ff9b::169.254.169.254 (NAT64, RFC 6052) True 169.254.169.254
::169.254.169.254 (IPv4-compatible) True 169.254.169.254

On a network with NAT64 (common in IPv6-only / DNS64 clusters, e.g. GKE), a URL such as
http://[64:ff9b::169.254.169.254]/computeMetadata/v1/ passes the is_global check and is
routed by NAT64 to the internal 169.254.169.254 metadata endpoint — an SSRF that can reach the
cloud metadata server / other link-local and private ranges.

The existing IPv4-mapped (::ffff:a.b.c.d) and 6to4 (2002::/16) forms are already reported as
non-global by ipaddress, so they were handled; NAT64 and the deprecated IPv4-compatible form are
the gaps.

Fix

Add _embedded_ipv4() which extracts the embedded IPv4 for IPv4-mapped, 6to4, NAT64
(64:ff9b::/96) and IPv4-compatible addresses, and reject the outer address when the embedded
IPv4 is not globally routable. Public NAT64 targets (e.g. 64:ff9b::8.8.8.8) remain allowed, so
there is no over-blocking of legitimate destinations.

Scope / honesty

Reaching the internal target requires the deployment network to actually route the embedded form
(NAT64 present, or a stack that routes IPv4-compatible IPv6). Where it doesn't route, this is
defense-in-depth completing the existing SSRF filter; where NAT64 is present it closes a real
metadata-SSRF path. Added regression tests for both forms.

load_web_page vets resolved addresses with `not address.is_global`, but
`is_global` on an IPv6 address does not reflect the embedded IPv4 target
for NAT64 (`64:ff9b::/96`) and IPv4-compatible (`::a.b.c.d`) addresses.
On a network with NAT64 (e.g. IPv6-only / DNS64 clusters), a URL like
`http://[64:ff9b::169.254.169.254]/` is treated as global and reaches the
internal 169.254.169.254 metadata endpoint. Extract the embedded IPv4 and
reject it when it is not globally routable.
@adilburaksen

Copy link
Copy Markdown
Contributor Author

Note: the same not address.is_global vetting (which this PR refines to also decode embedded IPv4) is being introduced for RestApiTool in #6003, so it will have the same NAT64/IPv4-compatible blind spot. It may be worth factoring _embedded_ipv4 (or the whole address-vetting routine) into a shared helper so both load_web_page and RestApiTool benefit from a single, complete implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant